/*-------------------------------------------------------------------------
 *
 * deparse_foreign_server_stmts.c
 *	  All routines to deparse foreign server statements.
 *
 * Copyright (c) Citus Data, Inc.
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "commands/defrem.h"
#include "lib/stringinfo.h"
#include "nodes/nodes.h"
#include "utils/builtins.h"

#include "distributed/citus_ruleutils.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/relay_utility.h"

static void AppendCreateForeignServerStmt(StringInfo buf, CreateForeignServerStmt* stmt);
static void AppendAlterForeignServerStmt(StringInfo buf, AlterForeignServerStmt* stmt);
static void AppendAlterForeignServerOptions(StringInfo buf, AlterForeignServerStmt* stmt);
static void AppendAlterForeignServerRenameStmt(StringInfo buf, RenameStmt* stmt);
static void AppendAlterForeignServerOwnerStmt(StringInfo buf, AlterOwnerStmt* stmt);
static void AppendDropForeignServerStmt(StringInfo buf, DropStmt* stmt);
static void AppendServerNames(StringInfo buf, DropStmt* stmt);
static void AppendBehavior(StringInfo buf, DropStmt* stmt);
static char* GetDefElemActionString(DefElemAction action);
static void AppendGrantOnForeignServerStmt(StringInfo buf, GrantStmt* stmt);
static void AppendGrantOnForeignServerServers(StringInfo buf, GrantStmt* stmt);

char* DeparseCreateForeignServerStmt(Node* node)
{
    CreateForeignServerStmt* stmt = castNode(CreateForeignServerStmt, node);

    StringInfoData str;
    initStringInfo(&str);

    AppendCreateForeignServerStmt(&str, stmt);

    return str.data;
}

char* DeparseAlterForeignServerStmt(Node* node)
{
    AlterForeignServerStmt* stmt = castNode(AlterForeignServerStmt, node);

    StringInfoData str;
    initStringInfo(&str);

    AppendAlterForeignServerStmt(&str, stmt);

    return str.data;
}

char* DeparseAlterForeignServerRenameStmt(Node* node)
{
    RenameStmt* stmt = castNode(RenameStmt, node);

    Assert(stmt->renameType == OBJECT_FOREIGN_SERVER);

    StringInfoData str;
    initStringInfo(&str);

    AppendAlterForeignServerRenameStmt(&str, stmt);

    return str.data;
}

char* DeparseAlterForeignServerOwnerStmt(Node* node)
{
    AlterOwnerStmt* stmt = castNode(AlterOwnerStmt, node);

    Assert(stmt->objectType == OBJECT_FOREIGN_SERVER);

    StringInfoData str;
    initStringInfo(&str);

    AppendAlterForeignServerOwnerStmt(&str, stmt);

    return str.data;
}

char* DeparseDropForeignServerStmt(Node* node)
{
    DropStmt* stmt = castNode(DropStmt, node);

    Assert(stmt->removeType == OBJECT_FOREIGN_SERVER);

    StringInfoData str;
    initStringInfo(&str);

    AppendDropForeignServerStmt(&str, stmt);

    return str.data;
}

char* DeparseGrantOnForeignServerStmt(Node* node)
{
    GrantStmt* stmt = castNode(GrantStmt, node);
    Assert(stmt->objtype == ACL_OBJECT_FOREIGN_SERVER);

    StringInfoData str = {0};
    initStringInfo(&str);

    AppendGrantOnForeignServerStmt(&str, stmt);

    return str.data;
}

static void AppendCreateForeignServerStmt(StringInfo buf, CreateForeignServerStmt* stmt)
{
    appendStringInfoString(buf, "CREATE SERVER ");
#ifdef DISABLE_OG_COMMENTS
    if (stmt->if_not_exists) {
        appendStringInfoString(buf, "IF NOT EXISTS ");
    }
#endif
    appendStringInfo(buf, "%s ", quote_identifier(stmt->servername));

    if (stmt->servertype) {
        appendStringInfo(buf, "TYPE %s ", quote_literal_cstr(stmt->servertype));
    }

    if (stmt->version) {
        appendStringInfo(buf, "VERSION %s ", quote_literal_cstr(stmt->version));
    }

    appendStringInfo(buf, "FOREIGN DATA WRAPPER %s ", quote_identifier(stmt->fdwname));

    AppendOptionListToString(buf, stmt->options);
}

static void AppendAlterForeignServerStmt(StringInfo buf, AlterForeignServerStmt* stmt)
{
    appendStringInfo(buf, "ALTER SERVER %s ", quote_identifier(stmt->servername));

    if (stmt->has_version) {
        appendStringInfo(buf, "VERSION %s ", quote_literal_cstr(stmt->version));
    }

    AppendAlterForeignServerOptions(buf, stmt);
}

static void AppendAlterForeignServerOptions(StringInfo buf, AlterForeignServerStmt* stmt)
{
    if (list_length(stmt->options) <= 0) {
        return;
    }

    appendStringInfoString(buf, "OPTIONS (");

    DefElemAction action = DEFELEM_UNSPEC;
    DefElem* def = NULL;
    foreach_declared_ptr(def, stmt->options)
    {
        if (def->defaction != DEFELEM_UNSPEC) {
            action = def->defaction;
            char* actionString = GetDefElemActionString(action);
            appendStringInfo(buf, "%s ", actionString);
        }

        appendStringInfo(buf, "%s", quote_identifier(def->defname));

        if (action != DEFELEM_DROP) {
            const char* value = quote_literal_cstr(defGetString(def));
            appendStringInfo(buf, " %s", value);
        }

        if (def != llast(stmt->options)) {
            appendStringInfoString(buf, ", ");
        }
    }

    appendStringInfoString(buf, ")");
}

static void AppendAlterForeignServerRenameStmt(StringInfo buf, RenameStmt* stmt)
{
    appendStringInfo(buf, "ALTER SERVER %s RENAME TO %s",
                     quote_identifier(strVal(stmt->object)),
                     quote_identifier(stmt->newname));
}

static void AppendAlterForeignServerOwnerStmt(StringInfo buf, AlterOwnerStmt* stmt)
{
    const char* servername = quote_identifier(strVal(stmt->object));
    appendStringInfo(buf, "ALTER SERVER %s OWNER TO ", servername);

    appendStringInfo(buf, "%s", RoleSpecString(stmt->newowner, true));
}

static void AppendDropForeignServerStmt(StringInfo buf, DropStmt* stmt)
{
    appendStringInfoString(buf, "DROP SERVER ");

    if (stmt->missing_ok) {
        appendStringInfoString(buf, "IF EXISTS ");
    }

    AppendServerNames(buf, stmt);

    AppendBehavior(buf, stmt);
}

static void AppendServerNames(StringInfo buf, DropStmt* stmt)
{
    List* serverValue = NULL;
    foreach_declared_ptr(serverValue, stmt->objects)
    {
        const char* serverString = quote_identifier(strVal(serverValue));
        appendStringInfo(buf, "%s", serverString);

        if (serverValue != llast(stmt->objects)) {
            appendStringInfoString(buf, ", ");
        }
    }
}

static void AppendBehavior(StringInfo buf, DropStmt* stmt)
{
    if (stmt->behavior == DROP_CASCADE) {
        appendStringInfoString(buf, " CASCADE");
    } else if (stmt->behavior == DROP_RESTRICT) {
        appendStringInfoString(buf, " RESTRICT");
    }
}

static char* GetDefElemActionString(DefElemAction action)
{
    switch (action) {
        case DEFELEM_ADD: {
            return "ADD";
        }

        case DEFELEM_SET: {
            return "SET";
        }

        case DEFELEM_DROP: {
            return "DROP";
        }

        default:
            return "";
    }
}

static void AppendGrantOnForeignServerStmt(StringInfo buf, GrantStmt* stmt)
{
    Assert(stmt->objtype == ACL_OBJECT_FOREIGN_SERVER);
    AppendGrantSharedPrefix(buf, stmt);
    AppendGrantOnForeignServerServers(buf, stmt);
    AppendGrantSharedSuffix(buf, stmt);
}

static void AppendGrantOnForeignServerServers(StringInfo buf, GrantStmt* stmt)
{
    ListCell* cell = NULL;
    appendStringInfo(buf, " ON FOREIGN SERVER ");

    foreach (cell, stmt->objects) {
        char* servername = strVal(lfirst(cell));
        appendStringInfoString(buf, quote_identifier(servername));
        if (cell != list_tail(stmt->objects)) {
            appendStringInfo(buf, ", ");
        }
    }
}
